package cn.fanyamin.util.collection;

import cn.fanyamin.util.MathUtils;


/**
 * The Class TimingRingBuffer.
 *
 * @param <T> the generic type
 */
public class TimingRingBuffer<T> {
    
    /** The cell duration. */
    private int cellDuration;
    
    /** The cell count. */
    private int cellCount;
    
    /** The ring. */
    private RingBuffer<T> ring;
    
    /** The start time. */
    private long startTime;
    
    /** The max value merger. */
    private IMaxValueMerger<T> maxValueMerger;

    /**
     * Insert.
     *
     * @param timestamp the timestamp
     * @param item the item
     */
    public void insert(long timestamp, T item) {
        long distanceTime = timestamp - startTime;
        int idx = 0;
        if (distanceTime < 0) {
            return;
        }
        if (distanceTime == 0) {
            mergeAndInsert(idx, item);
            return;
        }

        long distanceCnt = MathUtils.divideAndFloor(distanceTime, cellDuration);

        if (distanceCnt >= cellCount * 2) {
            ring.clear();
            startTime = MathUtils.divideAndFloor(timestamp, cellDuration)
                    * cellDuration;
            ring.enqueue(item);
            return;
        }

        if (distanceCnt >= cellCount) {
            rollOver(distanceCnt);
            idx = cellCount - 1;
            mergeAndInsert(idx, item);
            return;
        } 
        
        idx = (int) (distanceCnt);
        mergeAndInsert(idx, item);
        
        

    }

    /**
     * Roll over
     * for example: distanceCnt is 15, so we will add a12, a13, a14, a15 
     *  and remove original a0, a1, a2, a3
     *          a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, total 12 
     *
     * @param distanceCnt the distance cnt
     */
    private void rollOver(long distanceCnt) {
        for (int i = cellCount; i <= distanceCnt; i++) {
            if (!ring.isEmpty()) {
                ring.dequeue();
            }
            startTime += cellDuration;
        }
    }

    /**
     * Merge and insert.
     *
     * @param idx the idx
     * @param item the item
     */
    private void mergeAndInsert(int idx, T item) {
        T ret = mergeWithOldValue(idx, item);
        this.ring.insert(idx, ret);
    }

    /**
     * Merge with old value.
     *
     * @param idx the idx
     * @param newItem the new item
     * @return the t
     */
    private T mergeWithOldValue(int idx, T newItem) {
        T oldItem = this.ring.get(idx);
        return merge(oldItem, newItem);
    }

    /**
     * Merge.
     *
     * @param oldItem the old item
     * @param newItem the new item
     * @return the t
     */
    private T merge(T oldItem, T newItem) {
        if (oldItem == null) {
            return newItem;
        }
        if (newItem == null) {
            return oldItem;
        }
        return maxValueMerger.merge(oldItem, newItem);
    }

}
